import { BehaviorSubject, EMPTY, Observable, count, filter, from, map, mergeMap, partition, shareReplay, toArray, zip } from "rxjs";
import { WASSheetCaller } from "../WASSheetCaller";
import { RecordData, createRecordDataObs } from "./RecordData";
import { TitleReader } from "./TitleReader";
import { readRowObs, writeCellObs } from "./util";
import * as _ from "lodash-es";

/**
 * 把表格数据以对象形式进行读写
 */
export class ORMSystem
{
    recordDataObs?: Observable<RecordData>
    sheetCaller
    titleReader
    /**
     * @param token 
     * @param wpsScriptWebhook 表的airscrpit脚本webhook
     * @param sheetName 子表名
     * @param titleRow 表头行,默认第一行就是
     * @param recordRow 内容开始行,默认第二行开始
     * @param loadingStep 内容读取步进,默认每次2行
     * @param maxCol 整个表格最大列数,默认50
     * @param checkCol 哪一列作为空值检查,允许检查到空值后停止加载,默认1
     */
    constructor(
        private token: string,
        private wpsScriptWebhook: string,
        private sheetName: string,
        private titleRow: number = 1,
        private recordRow: number = 2,
        private loadingStep: number = 2,
        private maxCol: number = 50,
        private checkCol: number = 1
    ){
        this.sheetCaller = new WASSheetCaller(this.token,this.wpsScriptWebhook)
        this.titleReader = new TitleReader(this.sheetCaller, this.sheetName, this.maxCol, this.titleRow)
    }

    /**
     * 清除缓存
     */
    cleanCache()
    {
        this.recordDataObs = undefined
        this.titleReader.cleanCache()
    }

    /**
     * 获取表格数据对象,每行数据就是一个record对象
     * @returns
     */
    getRecordDataObs()
    {
        if(!this.recordDataObs)
        {
            this.recordDataObs = this.loadRecordDataObs().pipe(shareReplay())
        }
        return this.recordDataObs
    }

    /**
     * 表格数据对象,写入数据到wps表格
     * @param rd 
     * @param data 
     * @returns 
     */
    writeRecordDataObs(rd: RecordData, data: Record<string,string>)
    {
        let cellObs = from(rd.rawData).pipe(
            map(cell =>({cell, newValue:data[cell.title]})),
            shareReplay()
        )
        let [cellNotChangeObs, cellForWriteObs] = partition(cellObs, ({newValue}) => _.isUndefined(newValue))
        let writeObs = cellForWriteObs.pipe(
            mergeMap(({cell, newValue}) => writeCellObs(this.sheetCaller, this.sheetName, cell, newValue))
        )
        return zip(
            writeObs.pipe(toArray()),
            cellNotChangeObs.pipe(map(({cell}) => cell), toArray())
        ).pipe(
            map(([cellList1, cellList2]) =>
            {
                let newRawData = _.concat(cellList1, cellList2)
                let nrd = new RecordData()
                nrd.index = rd.index
                nrd.rawData = newRawData
                return nrd
            })
        )
    }

    private loadRecordDataObs()
    {
        let titleRawObs = this.titleReader.getTitleRawObs()
        let loadStepSub = new BehaviorSubject<{row:number, step: number}>({row:this.recordRow, step:this.loadingStep})
        let runObs = loadStepSub.pipe(
            mergeMap(({row, step}) =>
            {
                let oneLineObs = readRowObs(this.sheetCaller, this.sheetName, row, row + step - 1, 1, this.maxCol)
                let recordDataObs = createRecordDataObs(oneLineObs, titleRawObs).pipe(shareReplay())
                let countValueObs = recordDataObs.pipe(count(v => !!v.getCellByCol(this.checkCol)?.value))
                return countValueObs.pipe(
                    mergeMap(countValue =>
                    {
                        if(countValue == 0)
                        {
                            setTimeout(() => loadStepSub.complete())
                            return EMPTY
                        }
                        setTimeout(() => loadStepSub.next({row: row + step, step}))
                        return recordDataObs
                    })
                )
            })
        )
        return runObs
    }
}